% ===========================================
%	Utils.
% ===========================================

define_max_int(A) :- A #=< 10.
define_min_int(A) :- A #>= -10.
define_mid_int(A) :- A #>= 0.
min_to_max(A) :- labeling([min(A)],[A]).
max_to_min(A) :- labeling([max(A)],[A]).

define_max_int(A,B) :- define_max_int(A), define_max_int(B).
define_min_int(A,B) :- define_min_int(A), define_min_int(B).
define_mid_int(A,B) :- define_mid_int(A), define_mid_int(B).
min_to_max(A,B) :- min_to_max(A), min_to_max(B).
max_to_min(A,B) :- max_to_min(A), max_to_min(B).

define_max_int(A,B,C) :- define_max_int(A,B), define_max_int(C).
define_min_int(A,B,C) :- define_min_int(A,B), define_min_int(C).
define_mid_int(A,B,C) :- define_mid_int(A,B), define_mid_int(C).
min_to_max(A,B,C) :- min_to_max(A,B), min_to_max(C).
max_to_min(A,B,C) :- max_to_min(A,B), max_to_min(C).

boolean(true) :- true.
boolean(false) :- true.

bool_type(b) :- true.
int_type(i) :- true.
string_type(s) :- true.
collection_type(c) :- true.

not_boolean(X, true) :- X=false.
not_boolean(X, false) :- X=true.

% ===========================================
%	>(A,B,true).
% ===========================================

% Const > Var
>(A,B,true) :- nonvar(A), var(B), !, define_max_int(A,B), define_mid_int(A,B), A#>B, max_to_min(B).
% Others
>(A,B,true) :- define_max_int(A,B), define_mid_int(A,B), A#>B, min_to_max(A,B).

% ===========================================
%	>=(A,B,true).
% ===========================================
	
% Const >= Var
>=(A,B,true) :- nonvar(A), var(B), !, define_max_int(A,B), define_mid_int(A,B), A#>=B, max_to_min(B).
% Others
>=(A,B,true) :- define_max_int(A,B), define_mid_int(A,B), A#>=B, min_to_max(A,B).

% ===========================================
%	<(A,B,true).
% ===========================================

<(A,B,true) :- >(B,A,true).

% ===========================================
%	=<(A,B,true).
% ===========================================

=<(A,B,true) :- >=(B,A,true).

% ===========================================
%	>(A,B,false), >=(A,B,false),
%   <(A,B,false), =<(A,B,false).
% ===========================================

>(A,B,false) :- =<(A,B,true).
>=(A,B,false) :- <(A,B,true).
<(A,B,false) :- >=(A,B,true).
=<(A,B,false) :- >(A,B,true).

% ===========================================
%	++(A,B,Result).
% ===========================================

+(A,B,R) :- define_max_int(A,B,R), define_mid_int(A,B,R), R#=A+B, min_to_max(A,B,R).

% ===========================================
%	-(A,B,Result).
% ===========================================

-(A,B,R) :- +(R,B,A).

% ===========================================
%	*(A,B,Result).
% ===========================================

*(A,B,R) :- define_max_int(A,B,R), define_mid_int(A,B,R), R#=A*B, min_to_max(A,B,R).

% ===========================================
%	/(A,B,Result).
% ===========================================

/(A,B,R) :- B#\=0, *(R,B,A).

% ===========================================
%	=(A,B,T,true).
% ===========================================

=(A,B,T,true) :- var(A), var(B), bool_type(T), A=true, B=true.
=(A,B,T,true) :- var(A), var(B), bool_type(T), A=false, B=false, !.
=(A,B,T,true) :- var(A), var(B), int_type(T), !, define_max_int(A,B), define_mid_int(A,B), A#=B, min_to_max(A,B).
=(A,B,T,true) :- var(A), var(B), collection_type(T), !, define_max_int(Len), define_mid_int(Len), min_to_max(Len), length(B,Len), A=B.
=(A,B,_,true) :- A=B.

% ===========================================
%	=(A,B,T,false).
% ===========================================

=(A,B,T,false) :- var(A), var(B), bool_type(T), A=true, B=false.
=(A,B,T,false) :- var(A), var(B), bool_type(T), A=false, B=true, !.
=(A,B,T,false) :- var(A), var(B), int_type(T), !, define_max_int(A,B), define_mid_int(A,B), A#\=B, min_to_max(A,B).
=(A,B,T,false) :- var(A), var(B), collection_type(T), !, define_max_int(Len1,Len2), define_mid_int(Len1,Len2), min_to_max(Len1,Len2), Len1#\=Len2, length(A,Len1), length(B,Len2).
=(A,B,_,false) :- var(A), nonvar(B), boolean(B), !, not_boolean(A,B).
=(A,B,_,false) :- nonvar(A), var(B), boolean(A), !, not_boolean(B,A).
=(A,B,_,false) :- var(A), nonvar(B), integer(B), !, define_max_int(A,B), define_mid_int(A,B), A#\=B, min_to_max(A,B).
=(A,B,_,false) :- nonvar(A), var(B), integer(A), !, define_max_int(A,B), define_mid_int(A,B), A#\=B, min_to_max(A,B).
=(A,B,_,false) :- var(A), nonvar(B), is_list(B), !, length(B,Len1), define_max_int(Len2), define_mid_int(Len2), Len1#\=Len2, min_to_max(Len2), length(A,Len2).
=(A,B,_,false) :- nonvar(A), var(B), is_list(A), !, length(A,Len1), define_max_int(Len2), define_mid_int(Len2), Len1#\=Len2, min_to_max(Len2), length(B,Len2).
=(A,B,_,false) :- A\=B.

% ===========================================
%	isSuperType(Base, Derived) - true, ���� ����� "Derived" ����� ��� ����� ������ ������ ��������� ������ "Base"
% ===========================================
isSuperType(Y,X) :- 
	extends(Y,X)
	;
	X \= Y,
	extends(Y,Z), extends(Z,X).

% ===========================================
%	OCL:: Object.oclIsTypeOf
% ===========================================
oclIsTypeOf(X, Type, true) :- var(X),X =Type,!.
oclIsTypeOf(X, Type, true) :- X =..[Type|_].
oclIsTypeOf(X, Type, false) :- not(oclIsTypeOf(X, Type, true)).


% ===========================================
%	OCL:: Object.oclIsTypeOf
% ===========================================
%oclIsTypeOf(X, Type, true) :- X =..[Type|_].
%oclIsTypeOf(X, Type, false) :- not(oclIsTypeOf(X, Type, true)).

% ===========================================
%	OCL:: Object.oclIsKindOf
% ===========================================
oclIsKindOf(X, Type, true) :- var(X),X =Type,!.
oclIsKindOf(X, Type, true) :- 
	X =..[Type|_]
	;
	X =.. [Kind|_], isSuperType(Kind, Type),!.
oclIsKindOf(X, Type, false) :-not(oclIsKindOf(X, Type, true)).



% ===========================================
%	OCL:: Object.oclIsKindOf
% ===========================================
%oclIsKindOf(X, Type, true) :- 
%	X =..[Type|_]
%	;
%	X =.. [Kind|_], isSuperType(Kind, Type),!.
%oclIsKindOf(X, Type, false) :-not(oclIsKindOf(X, Type, true)).


% ===========================================
% 	OCL Object::oclIsUndefined
% ===========================================
isUndefined(null,true).
isUndefined(X,false):-var(X), !, X=not_null.
isUndefined(X,false):-X\=null.


% ===========================================
%	OCL:: =
% ===========================================
%=(A,B,true) :-A=B.
%=(A,B,false) :-A\=B.

% ===========================================
%	OCL:: and
% ===========================================
and(true,true,true):-true,!.
and(A,B,true):- A,B.
and(false,false,false):-true,!.
and(A,B,false):- not(and(A,B,true)).

% ===========================================
%	OCL:: or
% ===========================================
or(true,true,true):-true,!.
or(A,B,true):- A;B.
or(false,false,false):-true,!.
or(A,B,false):- not(or(A,B,true)).

% ===========================================
%	OCL:: includes
% ===========================================

includes( List, Obj, true):-member(Obj,List).
includes( List, Obj, false):-not(member(Obj,List)).

% ===========================================
%	OCL:: size
% ===========================================
size(List,Size):-length(List, Size).

% ===========================================
%	Auxiliary operation:: add
% ===========================================
add(C,Y,L):-var(Y), add(C,[],L).
add(C,Y,[C|Y]).

% ===========================================
%	Auxiliary operation:: find
% ===========================================

find(B,[],[]).
find(B,[H|Tail],L):-find(B,Tail,L1),var(H),add(B,L1,L).
find(B,[H|Tail],L):-find(B,Tail,L1),nonvar(H),add(H,L1,L).

% ===========================================
%	Auxiliary operation:: doList
% ===========================================
doList(P):-E=..P, call(E).

% ===========================================
%	OCL:: forall
% ===========================================
forall(Exp,[],true).
forall(Exp,[H|T],true):-Exp=..LExp, find(H, LExp, Lres),!, doList(Lres),forall(Exp,T,true).
forall(Exp,List,false):-not(forall(Exp,List,true)).

% ===========================================
%	OCL:: select
% ===========================================

select(Exp,[],[]).
select(Exp,[H|T],Res):-select(Exp,T,Res1),Exp=..LExp, find(H, LExp, Lres), doList(Lres),!, add(H,Res1,Res).
select(Exp,[H|T],Res):-select(Exp,T,Res1), Res=Res1.

% ===========================================
%	Auxiliary operation:: isList
% ===========================================
isList(P, X):-E=..P, X is E.

% ===========================================
%	OCL:: collect
% ===========================================

collect(Exp,[],[],true).
collect(Exp,[H|T],Res,true):-collect(Exp,T,Res1,true),Exp=..LExp, find(H, LExp, Lres), isList(Lres,X),!, add(X,Res1,Res).
collect(Exp,[H|T],Res,true):-collect(Exp,T,Res1,true), Res=Res1.

% ===========================================
%	>(A,B,Result).
% ===========================================
%>(A,B,true) :- A#>B.
%>(A,B,false) :- A#=<B.

% ===========================================
%	>=(A,B,Result).
% ===========================================
%>=(A,B,true) :- A#>=B.
%>=(A,B,false) :- A#<B.

% ===========================================
%	<(A,B,Result).
% ===========================================
%<(A,B,true) :- A#<B.
%<(A,B,false) :- A#>=B.

% ===========================================
%	<=(A,B,Result).
% ===========================================
%=<(A,B,true) :- A#=<B.
%=<(A,B,false) :- A#>B.

% ===========================================
%	OCL Collection::isEmpty
% ===========================================
isEmpty([], true).
isEmpty([_|_], false).


%extends(data2Node, abstractNode).
% Self=data2Node(name->name2,x->5,y->6).
% Node=dataNode(name->node2,children->Children,x->6,y->6), 
%	Children=[dataNode(name->child1), dataNode(name->child2)].
% Node=dataNode(name->node2,child->dataNode(name->child1),x->6,y->6).